﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using NLog;
using System.IO;
using System.Text.RegularExpressions;

namespace NGinnBPM.Lib.Util
{
    /// <summary>
    /// Configuration info provider.
    /// It looks for configuration (properties) file in 
    /// application base (bin) directory and above.
    /// Additionally, provides some configurations properties 
    /// such as ng.basedir, ng.machinename
    /// </summary>
    public class DefaultConfigProvider : NGinnBPM.Lib.Interfaces.IConfigParameterProvider
    {
        private Dictionary<string, string> _variables = new Dictionary<string, string>();
        private static Logger log = LogManager.GetCurrentClassLogger();
        private string _configFile = "nginn.properties";
        private bool _inited = false;
        private static Regex _re = new Regex(@"\$\{([\w._]+)\}");

        public DefaultConfigProvider()
        {
            SetInternal("basedir", Path.GetDirectoryName(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile));
            SetInternal("machinename", Environment.MachineName);
        }

        
        private string ResolveVariableInternal(string name)
        {
            lock (this)
            {
                if (!_inited)
                {
                    _inited = true;
                    LoadConfigFile();
                }
            }
            string val;
            if (!_variables.TryGetValue(name, out val))
                return null;
            return val;
        }

        private string SubstValue(string expr)
        {
            string v1;
            string v2 = expr;
            do
            {
                v1 = v2;
                v2 = _re.Replace(v1, new MatchEvaluator(ReplaceProperty));
            } while (v1 != v2);
            return v2;
        }

        private string ReplaceProperty(Match m)
        {
            Capture cval = m.Groups[1].Captures[0];
            string propName = cval.Value;
            return GetString(propName);
        }

        public void Set(string name, string value)
        {
            log.Debug("Setting: {0}={1}", name, value);
            _variables[name] = value;
        }

        private void SetInternal(string name, string value)
        {
            Set(name, value);
        }

        public string ConfigFile
        {
            get { return _configFile; }
            set { _configFile = value; }
        }

        
        private void LoadPropertiesFile(string fn)
        {
            using (StreamReader sr = new StreamReader(fn))
            {
                while (!sr.EndOfStream)
                {
                    string ln = sr.ReadLine();
                    if (ln == null || ln.Length == 0 || ln.StartsWith("#") || ln.StartsWith(";"))
                        continue;
                    if (ln.StartsWith("@include"))
                    {
                        string fninc = SubstValue(ln.Substring(9));
                        if (!Path.IsPathRooted(fninc))
                            fninc = Path.Combine(Path.GetDirectoryName(fn), fninc);
                        if (File.Exists(fninc))
                        {
                            log.Info("Including config file {0}", fninc);
                            LoadPropertiesFile(fninc);
                        }
                        else log.Info("Config: include file not found - ignoring: {0}", fninc);
                        continue;
                    }
                    int idx = ln.IndexOf('=');
                    if (idx <= 0)
                        throw new Exception("Invalid line in properties file " + fn + ": " + ln);
                    string name = ln.Substring(0, idx).Trim();
                    string val = ln.Substring(idx + 1).Trim();
                    Set(name, SubstValue(val));
                }
            }
        }

        protected void LoadConfigFile()
        {
            if (ConfigFile == null || ConfigFile.Length == 0)
                return;
            string dir = Path.GetDirectoryName(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
            DirectoryInfo di = new DirectoryInfo(dir);
            bool found = false;
            while (di != null && di.Parent != null && di != di.Parent)
            {
                string fn = Path.Combine(di.FullName, ConfigFile);
                log.Debug("Looking for config in {0}", fn);
                if (File.Exists(fn))
                {
                    Set("configfile", fn);
                    Set("configdir", di.FullName);
                    log.Debug("Loading config from {0}", fn);
                    LoadPropertiesFile(fn);
                    found = true;
                    break;
                }
                else
                {
                    di = di.Parent;
                }
            }
            if (!found)
            {
                log.Warn("Config file not found: {0}", ConfigFile);
            }
        }

        #region IConfigParameterProvider Members

        public string GetString(string paramName)
        {
            string s = ResolveVariableInternal(paramName);
            if (s == null) return null;
            return SubstValue(s);
        }

        public string GetString(string paramName, string defaultValue)
        {
            string s = GetString(paramName);
            return s == null ? defaultValue : s;
        }

        public int GetInt(string paramName, int defaultValue)
        {
            string s = GetString(paramName);
            return s == null || s.Length == 0 ? defaultValue : Convert.ToInt32(s);
        }

        public double GetDouble(string paramName, double defaultValue)
        {
            string s = GetString(paramName);
            return s == null || s.Length == 0 ? defaultValue : Convert.ToDouble(s);
        }

        public bool GetBool(string paramName, bool defVal)
        {
            string s = GetString(paramName);
            if (s == null) return defVal;
            s = s.ToUpper();
            return s == "1" || s == "TRUE" || s == "YES";
        }

        #endregion

        #region IConfigParameterProvider Members


        public IList<string> GetParameterNames()
        {
            return new List<string>(_variables.Keys);
        }

        #endregion

        #region IConfigParameterProvider Members


        public void Set(string param, int val)
        {
            Set(param, val.ToString());
        }

        public void Set(string param, double val)
        {
            Set(param, val.ToString());
        }

        public void Set(string param, bool val)
        {
            Set(param, val.ToString());
        }

        #endregion
    }
}
